﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Timers;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using pl.OC.Entity;
using System.Runtime.CompilerServices;
using System.IO.Compression;

namespace pl.OC.DB
{
    public class DBHandler
    {
        #region Class members

        private bool                m_isDBInitialized;
        private Timer               m_timer;
        private FileInfo            m_dbFile;

        private static DBHandler    ms_instance;

        private WorkSheet           m_workSheet;

        private DateTime            m_lastUpdatedTime;

        #endregion

        #region Delegates/Events

        public delegate void DBCreatedDelegate(FileInfo dbFile, WorkSheet worksheet);
        public event DBCreatedDelegate OnDBCreated;

        public delegate void DBOpenedDelegate(FileInfo dbFile, WorkSheet worksheet);
        public event DBOpenedDelegate OnDBOpened;

        public delegate void DBAutoSaveDelegate(FileInfo dbFile, WorkSheet worksheet);
        public event DBAutoSaveDelegate OnDBAutoSave;

        public delegate void DBEntryAddedDelegate(FileInfo dbFile, WorkSheet worksheet, WorkEntryComplexType entry);
        public event DBEntryAddedDelegate OnEntryAdded;

        public delegate void DBEntryUpdated(FileInfo dbFile, WorkSheet worksheet, WorkEntryComplexType entry, TimeSpan dt);
        public event DBEntryUpdated OnEntryUpdated;

        public delegate void StartWorkingDelegate();
        public event StartWorkingDelegate OnStartWorking;

        public delegate void EndWorkingDelegate();
        public event EndWorkingDelegate OnEndWorking;

        public delegate void SetThresholdDelegate(int OldValue, int NewValue, WorkSheet ws);
        public event SetThresholdDelegate OnThresholdChange;

        #endregion

        private DBHandler()
        {
            m_timer = new Timer(1000 * 5);
            m_timer.AutoReset = true;
            m_timer.Elapsed += new ElapsedEventHandler(m_timer_Elapsed);
        }

        void m_timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (OnStartWorking != null)
            {
                OnStartWorking();
            }

            if (!IsDBInitialized)
            {
                InitializeDB();
            }
            else
            {
                UpdateDate(m_workSheet);
                SerializeWorkSheet(m_dbFile, m_workSheet);
            }

            if (OnEndWorking != null)
            {
                OnEndWorking();
            }
        }

        public Double TimeInterval
        {
            get { return m_timer.Interval; }
            set { m_timer.Interval = value; }
        }

        public FileInfo DBFile
        {
            get { return m_dbFile; }
            set 
            {
                if (value != null && (m_dbFile == null || !m_dbFile.FullName.Equals(value.FullName)))
                {
                    m_dbFile            = value;
                    m_isDBInitialized   = false;

                    if (OnStartWorking != null)
                    {
                        OnStartWorking();
                    }
                    ForceInitialization();
                    if (OnEndWorking != null)
                    {
                        OnEndWorking();
                    }
                }
            }
        }

        public void Start()
        {
            m_timer.Start();
        }

        public void Stop()
        {
            m_timer.Stop();
        }

        public static DBHandler getInstance()
        {
            if (ms_instance == null)
            {
                ms_instance = new DBHandler();
            }
            return ms_instance;
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        protected WorkSheet DeserializeWorkSheet(FileInfo path)
        {
            if (path == null || !path.Exists)
            {
#if DEBUG
                System.Diagnostics.Debug.WriteLine("Trying to deserialize DB but it does not exist");
#endif
            }
            else
            {
                try
                {
                    XmlSerializer xmlserialzer = new XmlSerializer(typeof(pl.OC.Entity.WorkSheet));
                    XmlTextReader xmlTR = new XmlTextReader(path.FullName);
                    pl.OC.Entity.WorkSheet ws = (pl.OC.Entity.WorkSheet)xmlserialzer.Deserialize(xmlTR);
                    xmlTR.Close();
#if DEBUG
                    System.Diagnostics.Debug.WriteLine("DB deserialized");
#endif
                    return ws;
                }
                catch (Exception e)
                {
                    System.Diagnostics.Debug.WriteLine("An error occured while deserializing DB: " + e);
                }
                
            }

            return null;
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        protected void SerializeWorkSheet(FileInfo path, WorkSheet ws)
        {
            XmlTextWriter xmlTW = null;

            try
            {
                XmlSerializer xmlserialzer = new XmlSerializer(typeof(pl.OC.Entity.WorkSheet));
                xmlTW = new XmlTextWriter(path.FullName, Encoding.UTF8);

                xmlserialzer.Serialize(xmlTW, ws);

                if (OnDBAutoSave != null)
                {
                    OnDBAutoSave(m_dbFile, m_workSheet);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception occured while writing file: " + ex.Message);
            }
            finally
            {
                try
                {
                    xmlTW.Flush();
                    xmlTW.Close();
                }
                catch { }
            } 
        }

        public void InitializeDB()
        {
#if DEBUG
            System.Diagnostics.Debug.WriteLine("Initializing DB");
#endif
            m_workSheet = DeserializeWorkSheet(m_dbFile);
            if (m_workSheet == null)
            {
#if DEBUG
                System.Diagnostics.Debug.WriteLine("DB does not exist, creating new one");
#endif
                m_workSheet                     = new WorkSheet();
                m_workSheet.OvertimeThreshold   = 8;

                WorkEntryComplexType wsct       = new WorkEntryComplexType();
                wsct.startTime                  = DateTime.Now;
                wsct.endTime                    = DateTime.Now;

                m_workSheet.WorkEntry           = new WorkEntryComplexType[] { wsct };

                SerializeWorkSheet(m_dbFile, m_workSheet);
#if DEBUG
                System.Diagnostics.Debug.WriteLine("DB created");
#endif
                if (OnDBCreated != null)
                {
                    OnDBCreated(m_dbFile, m_workSheet);
                }
            }
            else
            {
                AddNewEntry(m_workSheet, DateTime.Now);

                if (OnDBOpened != null)
                {
                    OnDBOpened(m_dbFile, m_workSheet);
                }
            }
            m_isDBInitialized = true;
            m_lastUpdatedTime = DateTime.Now;
        }

        public int OvertimeThreshold
        {
            get { return m_workSheet.OvertimeThreshold; }
            set 
            {
                if (!m_isDBInitialized)
                {
                    throw new NullReferenceException("Threshold is stored in DB file, so please initialize it");
                }

                if (m_workSheet.OvertimeThreshold != value)
                {
                    if (OnThresholdChange != null)
                    {
                        int OldValue                    = m_workSheet.OvertimeThreshold;
                        m_workSheet.OvertimeThreshold   = value; 
                        OnThresholdChange(OldValue, value, m_workSheet);
                        
                    }
                }
            }
        }

        protected WorkEntryComplexType IsDateCreated(WorkSheet ws, DateTime dt)
        {
            for (int i = ws.WorkEntry.Length - 1; i >= 0; --i)
            {
                DateTime startTime = ws.WorkEntry[i].startTime;
                TimeSpan ts = dt - startTime;

                if (dt.Day == startTime.Day && dt.Month == startTime.Month && dt.Year == startTime.Year)
                {
                    return ws.WorkEntry[i];
                }
            }
            return null;
        }

        protected void UpdateDate(WorkSheet ws)
        {
            WorkEntryComplexType wsct = IsDateCreated(ws, DateTime.Now);

            if (wsct != null)
            {
                wsct.endTime        = DateTime.Now;
                TimeSpan DeltaTime  = DateTime.Now - m_lastUpdatedTime;
                m_lastUpdatedTime   = DateTime.Now;

#if DEBUG
                System.Diagnostics.Debug.WriteLine("Updating entry with starttime: " + wsct.startTime + " now is: " + DateTime.Now + " DT = " + DeltaTime);
#endif

                if (OnEntryUpdated != null)
                {
                    OnEntryUpdated(m_dbFile, ws, wsct, DeltaTime);
                }

            }
            else
            {
#if DEBUG
                System.Diagnostics.Debug.WriteLine("Updating entry (entry does not exist - creating new one)");
#endif
                AddNewEntry(ws, DateTime.Now);
                m_lastUpdatedTime = DateTime.Now;
            }
        }

        public void AddNewEntry(WorkSheet ws, DateTime dt)
        {
            WorkEntryComplexType[] table    = new WorkEntryComplexType[ws.WorkEntry.Length + 1];
            WorkEntryComplexType wsct       = new WorkEntryComplexType();
            wsct.startTime                  = dt;
            wsct.endTime                    = dt;

            for (int i = 0; i < ws.WorkEntry.Length; ++i)
            {
                table[i] = ws.WorkEntry[i];
            }
            table[table.Length - 1] = wsct;

            ws.WorkEntry = table;

            if (OnEntryAdded != null)
            {
                OnEntryAdded(m_dbFile, ws, wsct);
            }

#if DEBUG
            System.Diagnostics.Debug.WriteLine("New entry added: " + wsct.endTime);
#endif
        }

        public FileInfo CreateBackup()
        {
            return Compress(m_dbFile, null);
        }

        public FileInfo CreateBackup(FileInfo destinationFile)
        {
            return Compress(m_dbFile, destinationFile);
        }

        public FileInfo ReadFromBackup(FileInfo fileToBackup, FileInfo destinationFile)
        {
            return Decompress(fileToBackup, ref destinationFile);
        }

        public void ForceInitialization()
        {
            InitializeDB();
        }

        public bool IsDBInitialized
        {
            get { return m_isDBInitialized; }
        }

        public FileInfo Compress(FileInfo input, FileInfo output) 
        {
            FileStream fileInput    = null;
            FileStream fileOutput   = null;
            GZipStream gzipStream   = null;
            Int32 count             = 0;
            Byte[] buffer;

            if (output == null)
            {
                String outputFileName   = input.FullName.Substring(0, input.FullName.LastIndexOf(".")) + GetTimestamp(DateTime.Now) + ".gzip";
                output                  = new FileInfo(outputFileName);
            }

            try
            {
                fileOutput      = new FileStream(output.FullName, FileMode.Create, FileAccess.Write, FileShare.None);
                gzipStream      = new GZipStream(fileOutput, CompressionMode.Compress, true);
                fileInput       = new FileStream(input.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                buffer          = new byte[fileInput.Length];
                count           = fileInput.Read(buffer, 0, buffer.Length);
                
                fileInput.Close();
                fileInput       = null;
                
                gzipStream.Write(buffer, 0, buffer.Length);
            }
            catch (Exception ex)
            {
#if DEBUG
                System.Diagnostics.Debug.WriteLine("ERROR MESSAGE:\n" + ex.Message);
#endif
                throw ex;
            }
            finally
            {
                if (gzipStream != null)
                {
                    gzipStream.Close();
                    gzipStream = null;
                }
                if (fileOutput != null)
                {
                    fileOutput.Close();
                    fileOutput = null;
                }
                if (fileInput != null)
                {
                    fileInput.Close();
                    fileInput = null;
                }
            }

            return output;
        }

        public FileInfo Decompress(FileInfo input, ref FileInfo output)
        {
            FileStream fileInput        = null;
            FileStream fileOutput       = null;
            GZipStream gzipStream       = null;
            const Int32 bufferSize      = 4096;
            Byte[] buffer               = new Byte[bufferSize];
            Int32 count                 = 0;

            if (output == null)
            {
                String outputFileName   = input.FullName.Substring(0, input.FullName.LastIndexOf(".")) + ".xml";
                output                  = new FileInfo(outputFileName);
            }

            try
            {
                fileInput       = new FileStream(input.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
                fileOutput      = new FileStream(output.FullName, FileMode.Create, FileAccess.Write, FileShare.None);
                gzipStream      = new GZipStream(fileInput, CompressionMode.Decompress, true);
                while (true)
                {
                    count = gzipStream.Read(buffer, 0, bufferSize);
                    if (count != 0)
                    {
                        fileOutput.Write(buffer, 0, count);
                    }
                    if (count != bufferSize)
                    {
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
#if DEBUG
                System.Diagnostics.Debug.WriteLine("ERROR MESSAGE:\n" + ex.Message);
#endif
                throw ex;
            }
            finally
            {
                if (gzipStream != null)
                {
                    gzipStream.Close();
                    gzipStream = null;
                }
                if (fileOutput != null)
                {
                    fileOutput.Close();
                    fileOutput = null;
                }
                if (fileInput != null)
                {
                    fileInput.Close();
                    fileInput = null;
                }
            }

            return output;
        }

        private String GetTimestamp(DateTime value)
        {
            return value.ToString("yyyyMMddHHmmssffff");
        }

        public WorkSheet Worksheet
        {
            get { return m_workSheet; }
        }

        public void ClearDB()
        {
            WorkEntryComplexType wsct   = new WorkEntryComplexType();
            wsct.startTime              = DateTime.Now;
            wsct.endTime                = DateTime.Now;

            m_workSheet.WorkEntry       = new WorkEntryComplexType[] { wsct };

            SerializeWorkSheet(m_dbFile, m_workSheet);
#if DEBUG
            System.Diagnostics.Debug.WriteLine("Cleanned DB");
#endif
            if (OnDBCreated != null)
            {
                OnDBCreated(m_dbFile, m_workSheet);
            }
        }

    }
}
